cmake_minimum_required(VERSION 2.8)
project(dg)

include(CTest)

# we need at least C++11 standard
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED on)

OPTION(LLVM_DG "Support for LLVM Dependency graph" ON)
OPTION(ENABLE_CFG "Add support for CFG edges to the graph" ON)
OPTION(NO_EXCEPTIONS "Compile with -fno-exceptions (ON by default)" ON)

message(STATUS "Build type: ${CMAKE_BUILD_TYPE}")

if (LLVM_DG)
	# for llvm dg we need cfg and postdom edges
	if (NOT ENABLE_CFG)
		message(STATUS "Enabling CFG edges due to llvm dg")
	endif()

	set(ENABLE_CFG ON)

	find_package(LLVM REQUIRED CONFIG)

	message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}")
	message(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}")
	message(STATUS "LLVM binaries: ${LLVM_TOOLS_BINARY_DIR}")

	set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${LLVM_DIR}")
	include(LLVMConfig)
	include(AddLLVM)

	message(STATUS "LLVM include dir: ${LLVM_INCLUDE_DIRS}")
	message(STATUS "LLVM libraries dir: ${LLVM_LIBRARY_DIRS}")
	message(STATUS "LLVM definitions: ${LLVM_DEFINITIONS}")

	# if we were provided a path to custom sources
	# use that path
	if (LLVM_SRC_PATH)
		include_directories(${LLVM_SRC_PATH}/include)
		message(STATUS "Looking for headers in given: ${LLVM_SRC_PATH}/include")
	else()
		include_directories(${LLVM_INCLUDE_DIRS})
		message(STATUS "Looking for headers in: ${LLVM_INCLUDE_DIRS}")
	endif()

	# if we were provided a path to custom build directory
	# use that
	if (LLVM_BUILD_PATH)
		link_directories(${LLVM_BUILD_PATH}/lib)

		# llvm-config.h
		include_directories("${LLVM_BUILD_PATH}/include")
		message(STATUS "Looking for libraries in given: ${LLVM_BUILD_PATH}/lib")
	else()
		link_directories(${LLVM_LIBRARY_DIRS})
		message(STATUS "Looking for libraries in: ${LLVM_LIBRARY_DIRS}")
	endif(LLVM_BUILD_PATH)

	add_definitions(${LLVM_DEFINITIONS})
	add_definitions(-DHAVE_LLVM)

	option(LLVM_LINK_DYLIB "Link with LLVM dynamically" ON)

	if (LLVM_LINK_DYLIB)
		message(STATUS "LLVM linking: dynamic")
		set(llvm_core LLVM)
	else()
		message(STATUS "LLVM linking: static")
		if (${LLVM_PACKAGE_VERSION} VERSION_GREATER "3.4")
			if (${LLVM_PACKAGE_VERSION} VERSION_LESS "10")
				# From LLVM 10, linking to these breaks the build on some systems
				# and it works just ignoring them
				llvm_map_components_to_libnames(llvm_core core)
				llvm_map_components_to_libnames(llvm_support support)
			endif()
			llvm_map_components_to_libnames(llvm_analysis analysis)
			llvm_map_components_to_libnames(llvm_irreader irreader)
			llvm_map_components_to_libnames(llvm_bitwriter bitwriter)
		else()
			llvm_map_components_to_libraries(llvm_core core)
			llvm_map_components_to_libraries(llvm_irreader irreader)
			llvm_map_components_to_libraries(llvm_bitwriter bitwriter)
			llvm_map_components_to_libraries(llvm_analysis analysis)
			llvm_map_components_to_libraries(llvm_support support)
		endif()
	endif()

	# LLVM 10 and newer require at least c++14 standard
	if (${LLVM_PACKAGE_VERSION} VERSION_GREATER "9.0")
		set(CMAKE_CXX_STANDARD 14)
		option(USE_CXX14 "Use C++14 standard" ON)
	endif()
endif(LLVM_DG)

if (SVF_DIR)
	set(HAVE_SVF ON)
	add_definitions(-DHAVE_SVF)
	set(SVF_LIBDIR ${SVF_DIR}/lib)

	if (SVF_INCLUDE)
	else()
		if (EXISTS "${SVF_DIR}/include/WPA/Andersen.h")
			set(SVF_INCLUDE ${SVF_DIR}/include)
		elseif (EXISTS "${SVF_DIR}/../include/WPA/Andersen.h")
			set(SVF_INCLUDE ${SVF_DIR}/../include)
		else()
			message(FATAL_ERROR "Did not find the directory with SVF headers")
		endif()
	endif()

	set(SVF_LIBS Svf Cudd)

	include_directories(${SVF_INCLUDE})
	link_directories(${SVF_LIBDIR} ${SVF_LIBDIR}/CUDD)

	if (NOT LLVM_LINK_DYLIB)
		if (${LLVM_PACKAGE_VERSION} VERSION_GREATER "3.4")
			llvm_map_components_to_libnames(llvm_transformutils transformutils)
		else()
			llvm_map_components_to_libraries(llvm_transformutils transformutils)
		endif()
	endif()

	message(STATUS "SVF dir: ${SVF_DIR}")
	message(STATUS "SVF libraries dir: ${SVF_LIBDIR}")
	message(STATUS "SVF include dir: ${SVF_INCLUDE}")
	message(STATUS "SVF libs: ${SVF_LIBS}")
endif(SVF_DIR)

if (GOOGLE_SPARSEHASH_DIR)
    if (NO_EXCEPTIONS)
        message(FATAL_ERROR "Google sparsehash needs -fexceptions, but exceptions are disabled (use -DNO_EXCEPTOINS=off)")
    else()
        set(SPARSEHASH_DIR ${GOOGLE_SPARSEHASH_DIR}/src/)
        if (EXISTS "${SPARSEHASH_DIR}/sparsehash/sparse_hash_map")
            include_directories("${SPARSEHASH_DIR}")
            message(STATUS "Found Google sparsehash in ${SPARSEHASH_DIR}" )
            message(STATUS "Adding include dir ${SPARSEHASH_DIR}")
            add_definitions(-DHAVE_GOOGLE_SPARSEHASH)
        else()
            message(WARNING "Google sparsehash NOT FOUND in ${GOOGLE_SPARSEHASH_DIR}" )
        endif()
    endif()
endif()

if (TSL_HOPSCOTCH_DIR)
    set(HOPSCOTCH_DIR ${TSL_HOPSCOTCH_DIR}/include/)
    if (EXISTS "${HOPSCOTCH_DIR}/tsl/hopscotch_map.h")
        include_directories("${HOPSCOTCH_DIR}" )
        message(STATUS "Found Tessil Hopscotch map in ${HOPSCOTCH_DIR}" )
        message(STATUS "Adding include dir ${HOPSCOTCH_DIR}")
        add_definitions(-DHAVE_TSL_HOPSCOTCH)
    else()
        message(WARNING "Tessil hopscotch map NOT FOUND in ${TSL_HOPSCOTCH_DIR}" )
    endif()
endif()



if (ENABLE_CFG)
	add_definitions(-DENABLE_CFG)
endif()

message(STATUS "Using compiler: ${CMAKE_CXX_COMPILER}")

# Fuzzing
include(CheckCXXCompilerFlag)
set(CMAKE_REQUIRED_FLAGS "-fsanitize=fuzzer")
check_cxx_source_compiles(
	"#include <cstddef>
	extern \"C\"
	int LLVMFuzzerTestOneInput(const void * /*d*/, size_t /*s*/) { return 0; }"
	fuzzer_works)
set(CMAKE_REQUIRED_FLAGS "")

if (fuzzer_works)
	option(TESTING_ENABLE_FUZZING "Enable fuzzing tests" ON)
	message(STATUS "Will build fuzzing tests")
else()
	message(STATUS "Will NOT build fuzzing tests (available only with Clang 6 or newer)")
endif()

# explicitly add -std=c++11 (-std=c++14) and -fno-rtti
# we have CMAKE_CXX_STANDARD, but for some reason it does not
# put the -std=c++11 (-std=c++14) or -std=gnu++11 (-std=gnu++14)
# to the flags on some systems.
# For the -fno-rtti: LLVM still got problems with turning RTTI off...
if (USE_CXX14)
	set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14")
else()
	set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
endif()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-rtti -Wall -Wextra")
if (NO_EXCEPTIONS)
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-exceptions")
endif()

if (USE_CLANG_SANITIZERS)
	message(WARNING "USE_CLANG_SANITIZERS has been renamed to USE_SANITIZERS")
	set(USE_SANITIZERS on)
endif()

if (USE_SANITIZERS)
	set(CMAKE_REQUIRED_FLAGS "-fsanitize=undefined,address") # for linker
	check_cxx_compiler_flag("-fsanitize=undefined,address" sanitizers_work)
	set(CMAKE_REQUIRED_FLAGS "")

	if (sanitizers_work)
		set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined,address")
		add_definitions(-DUSING_SANITIZERS)
	else()
		message(WARNING "Used compiler does not support sanitizers or its support is incomplete.")
	endif()
endif()

# Debug Release RelWithDebInfo MinSizeRel.
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
	add_definitions(-DDEBUG_ENABLED)
	message(STATUS "Using compilation flags: ${CMAKE_CXX_FLAGS_DEBUG}")
elseif (CMAKE_BUILD_TYPE STREQUAL "Release")
	message(STATUS "Using compilation flags: ${CMAKE_CXX_FLAGS_RELEASE}")
elseif (CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")
	message(STATUS
		"Using compilation flags: ${CMAKE_CXX_FLAGS_RELWITHDEBINFO}")
elseif (CMAKE_BUILD_TYPE STREQUAL "MinSizeRel")
	message(STATUS "Using compilation flags: ${CMAKE_CXX_FLAGS_MINSIZEREL}")
endif ()

message(STATUS "Additional compilation flags: ${CMAKE_CXX_FLAGS}")

include(GNUInstallDirs)
message(STATUS "CMAKE_INSTALL_LIBDIR: \"${CMAKE_INSTALL_LIBDIR}\"")
message(STATUS "CMAKE_INSTALL_INCLUDEDIR: \"${CMAKE_INSTALL_INCLUDEDIR}\"")

add_subdirectory(lib)
add_subdirectory(tools)
add_subdirectory(tests EXCLUDE_FROM_ALL)

install(DIRECTORY include/
	DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
